package com.xtinno.service.impl;

import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.logging.Level;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.xtinno.constant.LLMConstants;
import com.xtinno.pojo.ModelRequest;
import com.xtinno.pojo.dto.LawContentDTO;
import com.xtinno.pojo.dto.LawReasoningRequestDTO;
import com.xtinno.pojo.dto.LawReasoningResponseDTO;
import com.xtinno.pojo.llm.VLLMResponseDTO;
import com.xtinno.pojo.po.LawReasoningKnowledgePO;
import com.xtinno.pojo.po.LawReasoningPromptPO;
import com.xtinno.prompt.PromptFormat;
import com.xtinno.service.CopyModelService;
import com.xtinno.service.LawReasoningKnowledgeService;
import com.xtinno.service.LawReasoningPromptService;
import com.xtinno.service.LawReasoningService;
import lombok.extern.java.Log;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.util.Strings;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

/**
 * <p>
 *     法律推导的主要业务逻辑类，这个业务的实现覆盖了所有场景下的法律推导业务逻辑.
 *
 * <p>
 *     可以参考 {@link @root/src/推导能力详细设计说明书_1.4.docx} 文档.
 *
 * @author lizhen
 * @since 1.0.0
 */
@Service
@Log
public class LawReasoningServiceImpl implements LawReasoningService {
	@Autowired
	private LawReasoningPromptService lawReasoningPromptService;

	@Autowired
	private LawReasoningKnowledgeService lawReasoningKnowledgeService;

	// TODO: 记得处理下大模型的调用代码.
	@Autowired
	private CopyModelService modelService;

	@Value("${model.name:Qwen/Qwen2.5-7B-Instruct}")
	private String model;

	@Value("${model.stream:false}")
	private Boolean stream;

	@Value("${model.temperature:0.2}")
	private Double temperature;

	@Value("${model.seed:42}")
	private Integer seed;

	@Value("${model.top_p:0.9}")
	private float top_p;

	/**
	 * 法条推导的统一入口方法.
	 * TODO:搞掉这个 DTO.
	 *
	 * @param dto 法条推导返回的结果.
	 */
	@Override
	public LawReasoningResponseDTO reasoning(LawReasoningRequestDTO dto) {
		// 0. 返回结果
		LawReasoningResponseDTO lawReasoningResponse = new LawReasoningResponseDTO();
		List<LawContentDTO> laws = new ArrayList<>(4);
		lawReasoningResponse.setLaws(laws);
//		List<LawReasoningResponseDTO.ReasoningLog> reasoningLogs = new ArrayList<>(4);
//		lawReasoningResponse.setLogs(reasoningLogs);

		// 1. 使用 场景 和 类型 参数，先获取用来做 法条推理 的 提示词、参数、返回结果.
		var lawReasoningPromptPO = lawReasoningPromptService.searchPrompt(dto.getScenario(), dto.getType());

		// 2. 对 lawReasoningPromptPO 做基本的校验.
		checkLawReasoningPromptPO(lawReasoningPromptPO);

		// 3. 检索法条知识库的法条
		List<LawReasoningKnowledgePO> lawKnowledgeList;
		if (dto.getLawKnowledgeMetadataCondition() != null) {
			String caseType = dto.getLawKnowledgeMetadataCondition().getCaseType();
			String caseCause = dto.getLawKnowledgeMetadataCondition().getCaseCause();
			lawKnowledgeList = lawReasoningKnowledgeService.searchKnowledge(caseType, caseCause, lawReasoningPromptPO.get().getLrp_flag());
		}
		else {
			lawKnowledgeList = Collections.emptyList();
		}

		// 4. 提示词组装
		String formatTemplate = lawReasoningPromptPO.get().getLrp_prompt();
		PromptFormat promptFormat = new PromptFormat(formatTemplate);
		/*
		 * 20250603 这里需要填充 2 个变量，一个是 paragraph，另一个是 paragraph_name.
		 * 这里是因为再入参中，传入的段落是灵活的，把段落的名称和内容都传入进来了，数据库配置的提示词会用到这 2 个变量。
		 */
		// 做输入变量的填充
		String paragraphVar = buildParagraphPromptVariablesNamedParagraph(dto.getParams().getParagraphs());
		String paragraphNameVar = buildParagraphPromptVariablesNamedParagraphName(dto.getParams().getParagraphs());
		promptFormat = promptFormat.fill("paragraph", paragraphVar);
		promptFormat = promptFormat.fill("paragraph_name", paragraphNameVar);

		/*
		 * 5. 开始调用大模型
		 * 这里需要把法条进行拆分来做调用，因为当法条数量太多的时候，模型本身的注意力会开始严重下降。
		 */

		// 5.1 先使用 batchSize 的模式推理出大致法条匹配的待选范围.
		int batchSize = lawReasoningPromptPO.get().getLrp_batch_size();
		String jsonSchema = lawReasoningPromptPO.get().getLrp_json_schema();
		// 核心方法，第一个步骤返回粗筛的法条结果范围.
		var conditionLaws = callLawReasoningInference(lawKnowledgeList, promptFormat, jsonSchema, batchSize);

		// 5.2 使用 5.1 的结果集，做精细化的法条推理.
		// 用来保存做 2 次精细化推理的知识库（待匹配法条）.
		List<LawReasoningKnowledgePO> conditionLawKnowledgeList = new ArrayList<>(conditionLaws.size());
		// 精细化知识库（待匹配法条）数据填充.
		for (var law : lawKnowledgeList) {
			for (var condition : conditionLaws) {
				// 比较 5.1 推理结果的 lp_id 和 法条的全部知识库的 lp_id，结果一致的就放入到 精细化知识库（待匹配法条） 中.
				if (law.getLrk_lp_id().equals(condition.getLpId())) {
					conditionLawKnowledgeList.add(law);
				}
			}
		}
		// 精细化推理是要 1个1个 推理的，这里可以考虑后面数据库打个标记，只有有标记的才做精细化的 2次 判断.
		batchSize = 1;
		var thinLaws = callLawReasoningInference(conditionLawKnowledgeList, promptFormat, jsonSchema, batchSize);

		// 5.3 使用 5.2 的结果集，根据业务给的提示词，做一个 通用 和 核心 法条的对比，目的是如果有一个法条是更详细的描述，那么还有
		//     通用的法条一并存在的话，这个通用法条就可以排除了.
		// 先构建要使用的知识库
		List<LawReasoningKnowledgePO> conditionLawKnowledgeListThin = new ArrayList<>(thinLaws.size());
		// 精细化知识库（待匹配法条）数据填充.
		for (var law : lawKnowledgeList) {
			for (var condition : thinLaws) {
				// 比较 5.1 推理结果的 lp_id 和 法条的全部知识库的 lp_id，结果一致的就放入到 精细化知识库（待匹配法条） 中.
				if (law.getLrk_lp_id().equals(condition.getLpId())) {
					conditionLawKnowledgeListThin.add(law);
				}
			}
		}

		String formatTemplateThin = lawReasoningPromptPO.get().getLrp_prompt_thin();
		PromptFormat promptFormatThin = new PromptFormat(formatTemplateThin);

		int batchSizeThin = thinLaws.size();
		String jsonSchemaThin = lawReasoningPromptPO.get().getLrp_json_schema();
		// 核心方法，第一个步骤返回粗筛的法条结果范围.
		var thinLawsThin = callLawReasoningInference(conditionLawKnowledgeListThin, promptFormatThin, jsonSchemaThin, batchSizeThin);

		// 5.4 后置处理了，从这里开始，就是程序的逻辑代码，和模型没啥关系了
		// 法条结果填充
		laws.addAll(thinLawsThin);

		// 日志后处理
		if (!dto.isLog()) {
			for (var law : laws) {
				law.setLog(null);
			}
		}
		else {
			if (dto.getLogLevel() <= 1) {
				for (var law : laws) {
					var log = law.getLog();
					if (log != null) {
						log.setIn(Strings.EMPTY);
						log.setOut(Strings.EMPTY);
					}
				}
			}
		}

		// 日志结果填充
//		if (dto.isLog()) {
//			reasoningLogs.addAll(pair.getRight());
//			reasoningLogs.addAll(thinPair.getRight());
//		}

		return lawReasoningResponse;
	}

	/**
	 * <p>
	 *     做法条推导的核心方法，方法传入了 知识库的数据、提示词的对象、json schema、知识库做批处理的大小 这几个参数.
	 * <p>
	 *     方法的逻辑是把知识库的数据做分批推理，具体的逻辑看方法里面的注释.
	 *
	 * @param lawKnowledgeList 法条知识库.
	 * @param promptFormat 提示词对象.
	 * @param jsonSchema json 架构.
	 * @param batchSize 批处理的大小.
	 * @return 推理后的结果，LawContent 是法条推理的结果，ReasoningLog 是推理过程日志.
	 */
	List<LawContentDTO> callLawReasoningInference(List<LawReasoningKnowledgePO> lawKnowledgeList, PromptFormat promptFormat, String jsonSchema, int batchSize) {
		// 知识库为空，返回空推理结果
		if (lawKnowledgeList.isEmpty()) {
			return Collections.emptyList();
		}
		// 知识库的总条数
		int totalSize = lawKnowledgeList.size();
		// 记录法条结果集合
		List<LawContentDTO> laws = new ArrayList<>(4);
		// 记录日志结果集合
//		List<LawReasoningResponseDTO.ReasoningLog> reasoningLogs = new ArrayList<>(4);
		// 重要，因为法条推导是一个分批推导的模式，所以为了相应的速度，这里做了并发的处理.
		List<Future<List<LawContentDTO>>> futures = new ArrayList<>(Math.ceilDiv(totalSize, batchSize));
		// new executor service.
		try (ExecutorService executor = Executors.newFixedThreadPool(Math.ceilDiv(totalSize, batchSize))) {
			for (int i = 0; i < totalSize; i += batchSize) {
				int end = Math.min(i + batchSize, totalSize);
				var subList = lawKnowledgeList.subList(i, end);
				// definition task.
				Callable<List<LawContentDTO>> task = () -> {
					List<LawContentDTO> innerLaws = new ArrayList<>();
					Optional<LawReasoningResponseDTO.ReasoningLog> reasoningLog = Optional.empty();
					try {
						// 做法条变量的替换，这个是一个固定的占位符，提示词中法条数据对应的占位符是：{law_knowledge}
						String lawKnowledge = buildLawKnowledgePromptVariables(subList);
						String prompt = promptFormat.format("law_knowledge", lawKnowledge);

						/*
						 * 法条推导目前设计的是一个隐式的工作流，这个隐式的目的是能够在第一步的推理中使用 think，然后第二步再转 json schema，这样
						 * 做的目的是为了，能够加入推理保持更稳定一点的输出，法条的备选项太多了，直接一步转成 json schema 的话，稳定性感觉没发修。
						 */

						// 1. 先使用 think 来生成法条推导的结果.
						String original = requestModel(prompt, null).getChoices().get(0).getMessage().getContent();
						int thinkLastIndex = original.lastIndexOf("</think>");
						String withoutThink = original;
						String think = Strings.EMPTY;
						if (thinkLastIndex != -1) {
							think = original.substring(0, thinkLastIndex);
							withoutThink = original.substring(thinkLastIndex + 8);
						}

						// 2. 把上一步的输出做 json schema.
						String jsonSchemaPrompt = "把下面的json内容原样输出。\njson内容：\n" + withoutThink;
						/*
						 * 返回结果主要是解析 content 中的内容，这个内容是一个 json，示例如下：
						 * [{"法规名称": "法规名称", "法条": "第几条"}]
						 * TODO: check
						 */
						String jsonStr = requestModel(jsonSchemaPrompt, jsonSchema).getChoices().get(0).getMessage().getContent();
						List<Map<String, Object>> list = JSON.parseObject(
								jsonStr,
								new TypeReference<List<Map<String, Object>>>() {
								}
						);

						// 法条对应的模型日志
						LawReasoningResponseDTO.ReasoningLog _log = new LawReasoningResponseDTO.ReasoningLog();
						_log.setIn(prompt);
						_log.setThink(think);
						_log.setOut(original);

						for (Map<String, Object> item : list) {
							String lawName = item.get("法规名称").toString();
							String tName = item.get("法条第几条").toString();
							// 构建返回结果 -- 法条
							LawContentDTO lawContent = new LawContentDTO();
							lawContent.setLawName(lawName);
							lawContent.setTName(tName);
							lawContent.setLog(_log);
							// 在法条知识库根据法条名称和条款项，反向查找 lp_id 等属性.
							for (var law : lawKnowledgeList) {
								// 判断法规名称相等
								if (lawName.replace("《", "").replace("》", "").equals(law.getLrk_l_name())) {
									// 判断条款项相等
									String tkx = law.getLrk_t_name() + law.getLrk_k_name() + law.getLrk_x_name();
									if (tName.equals(tkx)) {
										lawContent.setLId(law.getLrk_l_id());
										lawContent.setTId(law.getLrk_t_id());
										lawContent.setKId(law.getLrk_k_id());
										lawContent.setXId(law.getLrk_x_id());
										lawContent.setLpId(law.getLrk_lp_id());
										lawContent.setKSort(law.getLrk_k_sort());
										lawContent.setXSort(law.getLrk_x_sort());
										lawContent.setTkxText(law.getLrk_prov_text());
									}
								}
							}
							innerLaws.add(lawContent);
						}
					}
					catch (Exception e) {
						// 不能因为一次调用失败，毁掉整个调用的过程.
						log.log(Level.WARNING, "法条推导报错啦.", e);
					}

					return innerLaws;
				};

				// 添加集合，准备做批量的等待操作.
				futures.add(executor.submit(task));
			}
		}

		// 处理推理结果
		for (var future : futures) {
			try {
				var _laws = future.get();
				laws.addAll(_laws);
			}
			catch (Exception e) {
				log.log(Level.WARNING, "法条推导报错啦.", e);
			}
		}

		// 返回运行的结果
		return laws;
	}

	/**
	 * <p>
	 *     对 prompt对象 最基本的校验，规则如下：
	 * <p>
	 *     1. prompt对象 不为空。
	 * <p>
	 *     至于其他的参数，目前看起来，不是必要的参数，比如说 {@link LawReasoningPromptPO#lrp_input_variables} 和
	 *     {@link LawReasoningPromptPO#lrp_json_schema}，这 2 个可以先不需要校验，没有这 2 个其实运行起来也没啥大
	 *     问题.
	 *
	 * @param lawReasoningPromptPO 要做校验的 lawReasoningPromptPO 对象.
	 * @throws IllegalArgumentException 校验失败，message 包含了具体的失败信息.
	 */
	void checkLawReasoningPromptPO(Optional<LawReasoningPromptPO> lawReasoningPromptPO) throws IllegalArgumentException {
		if (lawReasoningPromptPO.isEmpty()) {
			throw new IllegalArgumentException("law_reasoning_prompt cannot be empty.");
		}

		if (Strings.isBlank(lawReasoningPromptPO.get().getLrp_prompt())) {
			throw new IllegalArgumentException("law_reasoning_prompt's prompt cannot be empty.");
		}
	}

	/**
	 * <p>
	 *     段落的对象 list，具体有啥看就行了.
	 *
	 * <p>
	 *     结果类似下面的字符串:
	 *
	 * <pre>{@code
	 *     <诉讼请求>
	 *     一、请求法院判令被告归还原告借款人民币本金100 ... 费全部由被告承担。
	 *     </诉讼请求>
	 *
	 *     <事实和理由>
	 *     ...
	 *     </事实和理由>
	 * }</pre>
	 *
	 * @param paragraphs the paragraph list.
	 * @return 看注释把.
	 */
	String buildParagraphPromptVariablesNamedParagraph(List<LawReasoningRequestDTO.Paragraph> paragraphs) {
		StringBuilder builder = new StringBuilder(100 * paragraphs.size());
		for (int i = 0; i < paragraphs.size(); i++) {
			LawReasoningRequestDTO.Paragraph paragraph = paragraphs.get(i);
			// <段落名称>
			builder.append("<").append(paragraph.getName()).append(">");
			// 换行
			builder.append(LLMConstants.LINE_BREAK);
			// 段落内容
			builder.append(paragraph.getValue().trim());
			// 换行
			builder.append(LLMConstants.LINE_BREAK);
			// </段落名称>
			builder.append("</").append(paragraph.getName()).append(">");
			// 换行 + // 换行
			if (i != paragraphs.size() - 1) {
				builder.append(LLMConstants.LINE_BREAK).append(LLMConstants.LINE_BREAK);
			}
		}
		return builder.toString();
	}

	/**
	 * <p>
	 *     段落的对象 list，具体有啥看就行了.
	 *
	 * <p>
	 *     结果类似下面的字符串，就是把段落的名称拼接起来:
	 *
	 * <pre>{@code
	 *     诉讼请求、事实和理由 ... 变更诉讼请求
	 * }</pre>
	 *
	 * @param paragraphs the paragraph list.
	 * @return 看注释把.
	 */
	String buildParagraphPromptVariablesNamedParagraphName(List<LawReasoningRequestDTO.Paragraph> paragraphs) {
		StringBuilder builder = new StringBuilder(10 * paragraphs.size());
		for (int i = 0; i < paragraphs.size(); i++) {
			LawReasoningRequestDTO.Paragraph paragraph = paragraphs.get(i);
			builder.append(String.format("<%s>", paragraph.getName()));
			if (i != paragraphs.size() - 1) {
				builder.append("、");
			}
		}
		return builder.toString();
	}

	/**
	 * 把传入的法条知识集合构建成提示词中要替换的文本，例如：
	 * <pre>{@code
	 *     1.《中华人民共和国民法典》第六百八十四条　保证合同的内容一 ... 等条款。适用范围：【合同】【典型合同】【保证合同】【一般规定】。
	 *     2.《中华人民共和国民法典》第六百九十三条　一般保 ... 保证人不再承担保证责任。适用范围：【合同】【典型合同】【保证合同】【保证责任】。
	 *     3.《中华人民共和国民法典》第四百零二条　以本法第 ... 权自登记时设立。适用范围：【物权】【担保物权】【抵押权】【一般抵押权】。
	 *     4.《中华人民共和国民法典》第六百七十六条　...
	 * }</pre>
	 *
	 * @param lawKnowledgeList 法条知识.
	 * @return 替换后的文本.
	 */
	String buildLawKnowledgePromptVariables(List<LawReasoningKnowledgePO> lawKnowledgeList) {
		StringBuilder builder = new StringBuilder(128 * lawKnowledgeList.size());
		for (int i = 0; i < lawKnowledgeList.size(); i++) {
			LawReasoningKnowledgePO law = lawKnowledgeList.get(i);
			// 对法条的内容做预处理，去除换行和空字符
			// eg. 1、
			builder.append(String.format("%s、", i + 1));
			// 法规名称，加 书名号 eg. 《最高人民法院关于审理民间借贷案件适用法律若干问题的规定（2020年修正）》
			builder.append(String.format("《%s》", law.getLrk_l_name()));
			// 第几条 第几款 第几项
			builder.append(law.getLrk_t_name() + law.getLrk_k_name() + law.getLrk_x_name());
			// 加一个空格
			builder.append(" ");
			// 条、款、项的 内容拼接起来，然后去掉换行
			builder.append((law.getLrk_t_text().replace(law.getLrk_t_name(), "").trim() + law.getLrk_k_text() + law.getLrk_x_text()).replace("\r", "").replace("\n", ""));
			// 适用范围 eg. 【婚姻家庭】【家庭关系】【夫妻关系】
			String fw = Strings.EMPTY;
			if (Strings.isNotBlank(law.getLrk_b_title())) {
				// 编
				fw += String.format("【%s】", law.getLrk_b_title());
			}
			if (Strings.isNotBlank(law.getLrk_z_title())) {
				// 章
				fw += String.format("【%s】", law.getLrk_z_title());
			}
			if (Strings.isNotBlank(law.getLrk_j_title())) {
				// 节
				fw += String.format("【%s】", law.getLrk_j_title());
			}
			// 编、章、节 都不为空的时候，才加这个使用范围，要不加上去也是空的
			if (!Strings.EMPTY.equals(fw)) {
				builder.append(String.format("适用范围：%s。", fw));
			}
			// 每一个法条一个换行
			builder.append(LLMConstants.LINE_BREAK);
		}
		return builder.toString();
	}

	/**
	 * 请求大模型
	 * @param prompt 提示词
	 */
	private VLLMResponseDTO requestModel(String prompt, String jsonSchema) {
		ModelRequest modelRequest = new ModelRequest();
		modelRequest.setModel(model);
		modelRequest.setStream(stream);
		modelRequest.setGuided_json(jsonSchema);
		modelRequest.setTemperature(temperature);
		modelRequest.setSeed(seed);
		modelRequest.setTop_p(top_p);
		ModelRequest.Message message = new ModelRequest.Message();
		message.setContent(prompt);
		message.setRole("user");
		modelRequest.setMessages(new ModelRequest.Message[]{message});
		return modelService.requestModel(modelRequest);
	}
}
